#include <linux/proc_fs.h>
#include <linux/device.h>
#include <linux/usb/composite.h>

#define BUFLEN 4096
static struct {
	struct semaphore limit_sem;
	char buffer[BUFLEN];
	int start;
	int end;
	struct usb_ctrlrequest *ctrlreq;
} ringbuffer;

static inline int next(int i) { return (i+16)%BUFLEN; }
static char ep0_ctrl_data[8];
static struct usb_request *ep0_read_ctrl_data_req;

#include "gadget_chips.h"

static void addtobuffer(void)
{
	struct usb_ctrlrequest *ctrl = ringbuffer.ctrlreq;
	char *buffer = ringbuffer.buffer;
	u16	index = le16_to_cpu(ctrl->wIndex);
	u16	value = le16_to_cpu(ctrl->wValue);
	u16	length = le16_to_cpu(ctrl->wLength);
	int i;
	
	#if 0
	printk("<3>type %02x, req %d, value %02x%02x, index %02x%02x, ",
		(int)ctrl->bRequestType,
		(int)ctrl->bRequest,
		(int)(value>>8),
		(int)(value&0xFF),
		(int)(index>>8),
		(int)(index&0xFF)
		);
	if(length)
	{
		printk("length %d, data ", (int)length);
		for(i=0;i<8;i++)
			printk("%02x", ep0_ctrl_data[i]);
	}
	printk("\n");
	#endif
	
	if( ( (ringbuffer.end+16)%BUFLEN ) != ringbuffer.start )
	{
		buffer[ringbuffer.end++] = ctrl->bRequestType;
		buffer[ringbuffer.end++] = ctrl->bRequest;
		buffer[ringbuffer.end++] = value>>8;
		buffer[ringbuffer.end++] = value&0xFF;
		buffer[ringbuffer.end++] = index>>8;
		buffer[ringbuffer.end++] = index&0xFF;
		buffer[ringbuffer.end++] = length>>8;
		buffer[ringbuffer.end++] = length&0xFF;

		for(i=0;i<8;i++)
			buffer[ringbuffer.end++] = ep0_ctrl_data[i];

		ringbuffer.end%=BUFLEN;
		
		up(&ringbuffer.limit_sem); // proc_read can continue.
	}
}

static void ep0_ctrl_data_read_complete(struct usb_ep *ep, struct usb_request *req)
{
	int i;
	if(req->status==0)
	{
		for(i=0;i<min(8u,req->actual);i++)
			ep0_ctrl_data[i] = ((char*)req->buf)[i];
		while(i<8)
			ep0_ctrl_data[i++] = 0;
	}
	addtobuffer();
}

/* this function is in atomic context */
static int pretreat(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
{
	if( ctrl->bRequestType != 0x48 && ctrl->bRequestType != 0x08 )
		return -1;

	ringbuffer.ctrlreq = (struct usb_ctrlrequest *)ctrl;
	if(le16_to_cpu(ctrl->wLength))
		return usb_ep_queue(gadget->ep0, ep0_read_ctrl_data_req, GFP_ATOMIC);
	addtobuffer();
	return 0;
}

#include "composite.c"
#include "usbstring.c"
#include "config.c"
#include "epautoconf.c"

#include "dummy_config.c"

#define DRIVER_VENDOR_NUM	0x0fc5
#define DRIVER_PRODUCT_NUM	0x1223

static struct usb_device_descriptor device_desc = {
	.bLength =		sizeof device_desc,
	.bDescriptorType =	USB_DT_DEVICE,

	.bcdUSB =		__constant_cpu_to_le16(0x0200),
	.bDeviceClass =		USB_CLASS_VENDOR_SPEC,

	.idVendor =		__constant_cpu_to_le16(DRIVER_VENDOR_NUM),
	.idProduct =		__constant_cpu_to_le16(DRIVER_PRODUCT_NUM),
	.bNumConfigurations =	1,
};

#define STRING_MANUFACTURER_IDX		0
#define STRING_PRODUCT_IDX		1

static struct usb_string strings_dev[] = {
	[STRING_MANUFACTURER_IDX].s = "Shanghai Jiao Tong University",
	[STRING_PRODUCT_IDX].s = "Emulate Delcom led light",
	{  }
};

static struct usb_gadget_strings stringtab_dev = {
	.language	= 0x0409,
	.strings	= strings_dev,
};

static struct usb_gadget_strings *dev_strings[] = {
	&stringtab_dev,
	NULL,
};

static int led_bind(struct usb_composite_dev *cdev)
{
	int			gcnum;
	struct usb_gadget	*gadget = cdev->gadget;
	int			id;

	id = usb_string_id(cdev);
	if (id < 0)
		return id;
	strings_dev[STRING_MANUFACTURER_IDX].id = id;
	device_desc.iManufacturer = id;

	id = usb_string_id(cdev);
	if (id < 0)
		return id;
	strings_dev[STRING_PRODUCT_IDX].id = id;
	device_desc.iProduct = id;

	dummy_add(cdev);

	gcnum = usb_gadget_controller_number(gadget);
	if (gcnum >= 0)
		device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum);
	else
		device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);

	ep0_read_ctrl_data_req = usb_ep_alloc_request(gadget->ep0, GFP_ATOMIC);
	if (!ep0_read_ctrl_data_req)
		return -ENOMEM;
	ep0_read_ctrl_data_req->length = 100;
	ep0_read_ctrl_data_req->buf = kmalloc(100, GFP_ATOMIC);
	ep0_read_ctrl_data_req->complete = ep0_ctrl_data_read_complete;
	if (!ep0_read_ctrl_data_req->buf) {
		usb_ep_free_request(gadget->ep0, ep0_read_ctrl_data_req);
		return -ENOMEM;
	}
	
	return 0;
}

static int led_unbind(struct usb_composite_dev *cdev){
	struct usb_gadget	*gadget = cdev->gadget;
	
	kfree(ep0_read_ctrl_data_req->buf);
	usb_ep_free_request(gadget->ep0, ep0_read_ctrl_data_req);
	return 0;
}

static struct usb_composite_driver led_driver = {
	.name		= "led",
	.dev		= &device_desc,
	.strings	= dev_strings,
	.bind		= led_bind,
	.unbind		= led_unbind,
};

MODULE_LICENSE("GPL");

int read_proc_entry(char *page, char **start, off_t offset, int count, int *eof, void *data)
{
	if(offset!=0) return 0;
	down(&ringbuffer.limit_sem);
	local_irq_disable(); // since ringbuffer.buffer is filled in atomic context, I cannot use mutex or semaphore.
	memcpy(page, ringbuffer.buffer+ringbuffer.start, 16);
	ringbuffer.start = (ringbuffer.start+16)%BUFLEN;
	local_irq_enable();
	*eof=1;
	return 16;
}

static int __init init(void)
{
	struct proc_dir_entry *entry = 0;
	
	entry = create_proc_entry("led", 0444, 0);
	if(entry == 0){
		return -1;
	}else{
		entry->read_proc = read_proc_entry;
		entry->owner = THIS_MODULE;
	}
	
	sema_init(&ringbuffer.limit_sem, 0);

	return usb_composite_register(&led_driver);
}
module_init(init);

static void __exit cleanup(void)
{
	remove_proc_entry("led", 0);
	usb_composite_unregister(&led_driver);
}
module_exit(cleanup);
